package wsepForum.network.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Vector;

import wsepForum.domain.Session;
import wsepForum.domain.SessionManager;
import wsepForum.logging.ForumLogger;
import wsepForum.logging.ForumLogger.LoggerCategory;
import wsepForum.network.common.ITokenizer;
import wsepForum.network.server.gate.IUIController;

/**
 * Manage a single client connection, hold its session,
 * and in charge of hanling the UI the right tokens/send data back to the client
 *
 *  Adjusted to support console connections
 *
 * @author Oren Ben-Haim
 */
public class ConnectionHandler<T> extends Thread {
	/*****************************
	 * MEMBERS                   *
	 *****************************/
	public static final int BUFFERSIZE = 35000;
	/** the socket of the client */
	private Socket clientsocket = null;
	/** in from the line */
	private BufferedReader in;
	/** out from the line */
	protected PrintWriter out = null;
	/**
	 * if true the connection should disconnect
	 */
	private boolean disconnectFlag;
	/**
	 * the connection's session
	 */
	protected Session session;
	/**
	 * the unit that breaks lines apart
	 */
	protected ITokenizer<T> tokenizer;
	/**
	 * The UI controller
	 */
	private IUIController<T> ui;


	protected String delimiter = "";

	/*****************************
	 * CONSTRUCTOR               *
	 *****************************/
	/**
	 * @param acceptedSocket the socket of the client
	 * @param connectionSession the connection's session
	 * @param uiController The UI controller
	 */
	public ConnectionHandler(Socket acceptedSocket,Session connectionSession, IUIController<T> uiController) {
		this.clientsocket = acceptedSocket;
		this.session = connectionSession;
		this.ui = uiController;
		this.disconnectFlag = false;
	}

	/*****************************
	 * METHODS                   *
	 *****************************/
	/**
	 * Main actor of the connection
	 */
	public void run()
	{
		ForumLogger logger = ForumLogger.getInstance();
		try
		{
			this.initialize();
		}
		catch (IOException e)
		{
			logger.error("Error in initializing I/O with " + this.clientsocket.getRemoteSocketAddress().toString(), LoggerCategory.NETWORK);
		}

		logger.info("Opened connection with " + this.clientsocket.getRemoteSocketAddress().toString(),LoggerCategory.NETWORK);
		try
		{
			this.process();
		}
		catch (IOException e)
		{}
		logger.info("Closing connection with " + this.clientsocket.getRemoteSocketAddress().toString(),LoggerCategory.NETWORK);
		if (!this.disconnectFlag) this.close();
	}

	/**
	 * main loop of the connection, process incoming data
	 * @throws IOException .
	 */
	private void process() throws IOException
	{
		// send welcome message if needed
		String msg = this.ui.getWelcome();
		if (msg != null)
			this.sendString(msg);
		Vector<String> respond;
		// read data from socket
		final char[] buffer = new char[ConnectionHandler.BUFFERSIZE];
		int charsRead = -1;
		while (((charsRead = this.in.read(buffer)) != -1) && !this.disconnectFlag)
		{
			if (charsRead <= 0)
				continue;

			// parse the bytes into a string
			msg = getString(buffer, charsRead) ;
			respond = new Vector<String>(1);
			// tokenize string for line tokens
			Vector<T> tokens = this.tokenizer.process(msg);
			if (tokens != null) {
				for (int i=0;i<tokens.size();i++) {

					respond.add(this.ui.getRequest(tokens.elementAt(i), this.session));
				}
			}
			// send data back to the client if needed
			if (respond != null)
			{
				for (final String send : respond)
				{
					if (send == null)
						return;
					// send responds to the line, if null hangup
					this.sendString(send);
				}
			}
		}
	}

	/**
	 * send data to the line
	 * @param str string to send
	 * @return true if sent
	 */
	public synchronized boolean sendString(String str)
	{
		try
		{
			// send string to the line
			this.out.write(str + delimiter);
			this.out.flush();
			return true;
		}
		catch (Exception e)
		{
			return false;
		}
	}

	/**
	 * prepare the connection to use
	 * @throws IOException .
	 */
	private void initialize() throws IOException
	{
		/**
		 * Initialize I/O
		 */
		this.in = new BufferedReader(new InputStreamReader(this.clientsocket.getInputStream()));
		this.out = new PrintWriter(this.clientsocket.getOutputStream(), true);
	}

	/**
	 * Closes the connection
	 */
	private void close()
	{
		try
		{
			if (this.in != null)
				this.in.close();
			if (this.out != null)
				this.out.close();
			this.clientsocket.close();
			SessionManager.getInstance().endSession(this.session.getSessionID());
			this.session.logout();
		}
		catch (IOException e)
		{/* ignore closing problem */}
	}

	/**
	 * kill the connection by force
	 */
	public void killConnection()
	{
		this.disconnectFlag = true;
		this.close();
	}

	/**
	 * Turns char array to a string
	 * @param buffer char buffer array to turn from
	 * @param size size of real array
	 * @return string got from the char array
	 */
	protected String getString(char[] buffer, int size) {
		return new String(buffer, 0, size);
	}

	public Session getSession() {
		return session;
	}

	

}
